package com.libnetwork.httpservice

import android.util.Log
import com.libbase.extension.decodeURI
import com.libbase.utils.JsonUtils
import com.libbase.utils.ThreadUtils
import com.libbase.utils.genericType
import com.libnetwork.HttpException
import com.libnetwork.Network
import com.libnetwork.httpservice.calladapter.RxJava3CallAdapterFactory
import com.moczul.ok2curl.CurlInterceptor
import com.moczul.ok2curl.logger.Logger
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import okhttp3.Call
import okhttp3.Callback
import okhttp3.CertificatePinner
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.mock.MockRetrofit
import retrofit2.mock.NetworkBehavior
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile
import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger

class HttpService private constructor() {
    companion object {
        val instance: HttpService by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            HttpService()
        }

        var successCode: Int = 200
    }

    val apiBaseUrl: String?
        get() = Network.instance.serviceAddressRepository?.data?.apiBaseUrl

    val h5BaseUrl: String?
        get() = Network.instance.serviceAddressRepository?.data?.h5BaseUrl

    val okHttpClient: OkHttpClient by lazy {
        val httpLoggingInterceptor = HttpLoggingInterceptor()
        OkHttpClient.Builder().apply {
            Network.instance.adapter?.certificates()?.let { certificates ->
                if (certificates.isNotEmpty()) {
                    certificatePinner(
                        CertificatePinner
                            .Builder().apply {
                                certificates.forEach { (host, certs) ->
                                    certs.forEach {
                                        add(host, CertificatePinner.pin(it))
                                    }
                                }
                            }
                            .build()
                    )
                }
            }
        }
            .addInterceptor { chain ->
                val request: Request =
                    Network.instance.serviceAddressRepository?.data?.apiBaseUrls?.let { apiBaseUrls ->
                        if (apiBaseUrls.any {
                                it.contains(
                                    chain.request().url.host
                                )
                            }) chain.request().newBuilder().apply {
                            Network.instance.token?.let { token ->
                                addHeader("Authorization",
                                    Network.instance.tokenType?.let { "$it $token" } ?: token)
                            }
                        }.apply {
                            Network.instance.adapter?.getHeaders(Network.instance)?.let {
                                it.forEach { (key, value) ->
                                    addHeader(key, value)
                                }
                            }
                        }.build()
                        else null
                    } ?: chain.request()

                chain.proceed(request)
            }
            .addInterceptor(httpLoggingInterceptor.apply {
                httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
            })
            .addInterceptor(CurlInterceptor(object : Logger {
                override fun log(message: String) {
                    Log.i("Ok2Curl", message)
                }
            }))
            .connectTimeout(15, TimeUnit.SECONDS)
            .readTimeout(15, TimeUnit.SECONDS)
            .writeTimeout(15, TimeUnit.SECONDS).build()
    }

    val retrofit: Retrofit by lazy {
        val builder =
            Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(JsonUtils.json))
                .addCallAdapterFactory(RxJava3CallAdapterFactory.create()).client(okHttpClient)

        apiBaseUrl?.let { builder.baseUrl(it) }

        builder.build()
    }

    val mockRetrofit: MockRetrofit by lazy {
        val networkBehavior = NetworkBehavior.create()
        networkBehavior.setDelay(0, TimeUnit.MILLISECONDS)
        networkBehavior.setErrorPercent(0)
        networkBehavior.setFailurePercent(0)
        networkBehavior.setVariancePercent(0)
        MockRetrofit.Builder(retrofit).networkBehavior(networkBehavior).build()
    }

    fun <T : Any> retry(observable: Observable<T>): Observable<T> {
        val maxRequestCount = 3

        return observable.retryWhen { error ->
            val requestCount = AtomicInteger(0)
            error.flatMap { err ->
                if (err is SocketTimeoutException && requestCount.incrementAndGet() < maxRequestCount) {
                    Observable.timer(1, TimeUnit.SECONDS)
                } else {
                    Observable.error(err)
                }
            }
        }.retryWhen { error ->
            error.flatMap { err ->
                Single.create { emmit ->
                    val adapter = Network.instance.adapter

                    if (err !is HttpException || err.code != 401 || adapter == null) {
                        emmit.onError(err)
                        return@create
                    }

                    val dataMap = err.response.body?.string()?.let {
                        JsonUtils.json.fromJson<Map<String, Any>>(
                            it, genericType<Map<String, Any>>()
                        )
                    }

                    if (dataMap?.get("code") != 100004) {
                        ThreadUtils.instance.executeInMainThread {
                            adapter.invalidToken(dataMap?.get("msg") as? String, null)
                        }
                        emmit.onError(err)
                        return@create
                    }

                    adapter.refreshToken {
                        if (it) {
                            emmit.onSuccess(true)
                        } else {
                            emmit.onError(err)
                        }
                    }
                }.toObservable()
            }
        }
    }

    fun download(
        url: String, filePath: String, startsPoint: Long = 0
    ): Observable<DownloadProgress> {
        val observable = Observable.create { emitter ->
            val requestBuilder: Request.Builder = Request.Builder().url(url)
            // 断点续传
            if (startsPoint > 0) {
                requestBuilder.header("RANGE", "bytes=$startsPoint-")
            }

            val okHttpClient: OkHttpClient =
                okHttpClient.newBuilder().addNetworkInterceptor { chain ->
                    val originalResponse = chain.proceed(chain.request())
                    originalResponse.newBuilder()
                        .body(DownloadResponseBody(originalResponse, startsPoint) {
                            emitter.onNext(it)
                        }).build()
                }.connectTimeout(60, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS)
                    .writeTimeout(60, TimeUnit.SECONDS).build()

            val call = okHttpClient.newCall(requestBuilder.build())
            call.enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    emitter.onError(e)
                }

                override fun onResponse(call: Call, response: Response) {
                    if (response.code != 200) {
                        emitter.onError(HttpException(response))
                        return
                    }

                    val file = File(filePath.decodeURI())
                    val parentFile = file.parentFile
                    if (parentFile == null) {
                        emitter.onError(NullPointerException("file.parentFile == null"))
                        return
                    }

                    if (!parentFile.exists()) {
                        parentFile.mkdirs()
                    }

                    val randomAccessFile = RandomAccessFile(file, "rwd")
                    randomAccessFile.seek(startsPoint)

                    var totalBytesRead = 0L
                    response.body?.byteStream()?.let { inputStream ->
                        val buffer = ByteArray(1024 * 8)
                        var len: Int
                        while (inputStream.read(buffer).also { len = it } != -1) {
                            randomAccessFile.write(buffer, 0, len)
                            totalBytesRead += len
                        }
                    }

                    emitter.onNext(
                        DownloadProgress(
                            1.0,
                            response.body?.contentLength() ?: 0,
                            totalBytesRead,
                            true,
                            filePath,
                            response
                        )
                    )

                    emitter.onComplete()
                }
            })

            emitter.setCancellable {
                call.cancel()
            }
        }

        return retry(observable)
    }
}